/*
 * ajax_manager.js
 *
 * Copyright (C) 2006 - OS3 srl - http://www.os3.it
 *
 * Written by: Fabio Rotondo - fabio.rotondo@os3.it
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * version 2 of the License ONLY.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * NOTE: this is the GPL version of the library. If you want to include this 
 *       library inside a CLOSED SOURCE / PROPRIETARY software, you need 
 *       to purchase a COMMERCIAL LICENSE. See http://www.os3.it/license/
 *       for more info.
 *
 *       2009-05-04:	ADD: new static function AJAXManager.handle_easy
 *
 *       2009-01-24:	ADD: this.cbacks for:
 *
 *       			- serialize	serialize data
 *       			- req-start	request start
 *       			- req-end	request end
 *       			- req-error	request error
 *
 *       		ENH: AJAX Manager is now more "liwe"ized
 *       		ENH: removed String and Array enhancement dependencies
 *
 *       		DEP: error_handler has been DEPRECATED
 *       		DEP: start_req_handler has been DEPRECATED
 *       		DEP: end_req_handler has been DEPRECATED
 *
 */

// {{{ base docs
/**
AJAX Manager
============

AJAX Manager implements all the needed functionalities you may need to use AJAX calls inside your web applications.

Inside the ``liwe.AJAX`` static class you can find a valid instance of AJAX Manager ready to be used in your apps.

.. class:: AJAXManager
	
	This is the AJAXManager class. You can instantiate as many :class:`AJAXManager` as you want.
	Usually, in your app, since AJAX requests are asyncronous, you only need one single :class:`AJAXManager`
	instance.
	As a bonus: ``liwe.AJAX`` offers an already instantiated :class:`AJAXManager` class for you to use.
**/
// }}}

function AJAXManager ()
{
	this._reqs = [];
	this._in_list = 0;
	this._in_abort = false;
// {{{ url - docs
/**
	.. attribute:: url

		This attribute is set by default to the same value of ``liwe.ajax_url``.
		Its value will be used with :meth:request with the  ``url`` param set to ``null``.

**/
// }}}
	this.url = liwe.ajax_url;

	// {{{ cbacks - docs
/**
	.. attribute:: cbacks

		Use this attribute to define custom :class:`AJAXManager` callbacks for special events.
		At the moment, you can specifiy events for the following:

			- ``serialize``:  this callback will be called before AJAX serialization.
					  The callback function should be defined as following:

						.. code-block:: javascript
	
							function serialize_callback ( string_value )
							{
								// do serialization on the string here

								// in the end
								return serialized_string;
							}

			- ``req-start``:  this callback is called when a new request will start.
					  The callback function should be defined as following:

						.. code-block:: javascript

							function reqstart_callback ( req )
							{
								// do what you want 
							}

			- ``req-end``:  this callback is called when a new request will start.
					  The callback function should be defined as following:

						.. code-block:: javascript

							function reqend_callback ( req )
							{
								// do what you want 
							}

			- ``req-error``:  this callback is called when an error occurs during an AJAX request.
					  The callback function should be defined as following:

						.. code-block:: javascript

							function reqerror_callback ( ajax_response )
							{
								// do what you want 
							}

**/
	// }}}
	this.cbacks = {	"serialize" : null, 
			"req-start" : null, 
			"req-end" : null, 
			"req-error" : null 
		      };

	// {{{ request ( url, vars, callback, easy, sync )
/**
	.. method:: request ( url, vars, callback, easy, sync )

		use this method to perform the standard (more complex) AJAX request.
		Request are sent using ``POST`` method. ``GET`` method is not supported.
		This uses the standard ``XMLHttpObject``.

		Variables you want to send to the ``url`` are set inside the ``vars`` variable.

		A standard request calls the ``callback`` function for each single request status change.
		Usually you only want to handle the end of the request (when the server has sent you the
		answer you were waiting for). In this case, set the ``easy`` param to ``true`` and you'll
		be notified only when the request succedes.

		By default, request are asyncronous, use ``sync`` to ``true``, if you want 
		to perform a sync request.

		:param url:	the url to send the AJAX request to. If ``url`` is not specified, the
				class :attr:`~AJAXManager.url` is used.

		:param vars:	vars is a dictionary (key/value pairs) containing all the variable / params you want to send
				using the AJAX call.

		:param callback: since by default AJAX requests are asyncronous, you can set a ``callback`` to be called when
				 the request changes its status. The callback function must be defined as following:

					.. code-block:: javascript
						
						// This is an example of a full request, that will be called for
						// each state change
						function req_complete ( req )
						{
							console.debug ( "REQ: status: %d", req.readyState );
							if ( req.readyState == 4 )
							{
								console.debug ( "Request complete" );
								alert ( req.responseText );
							}
						}

						// This is an example of an ``easy`` request callback that will
						// be called only when the request succedes.
						function req_easy ( v )
						{
							// v is a object key/value pairs containing the response
							console.debug ( "V: %o", v );
						}

		:param easy:	If you are only interested in successful notifications, set this value to ``true``.
				The callback must be created in this way:

					.. code-block:: javascript

						// This is an example of an ``easy`` request callback that will
						// be called only when the request succedes.
						function req_easy ( v )
						{
							// v is a object key/value pairs containing the response
							console.debug ( "V: %o", v );
						}

		:param sync:	Set this to ``true`` if you want to perform a syncronous AJAX request.
				Your JavaScript application will stop until the request is completed.
**/
	this.request = function ( url, vars, callback, easy, sync, err_cback ) 
	{
		var req = null;
		var id = '';
		var t;
		var res = '';
		var self = this;
		var s;

		if ( ! url ) url = this.url;

		if ( vars ) 
		{
			for ( t in vars ) 
			{
				if ( typeof ( vars [ t ] ) == 'undefined' ) continue;
				if ( typeof ( vars [ t ] ) == 'function' ) continue;
				if ( ( typeof ( vars [ t ] ) == 'object' ) && ( vars [ t ] == null ) ) continue;

				/*
					This is a hack for IE, since it considers functions as objects (!!!)
				*/
				if ( typeof ( vars [ t ] ) == 'object' )
				{
					s = vars [ t ].toString ();
					if ( s.match ( /^function/ ) ) continue;
				}

				if ( vars [ t ] == '__arr' ) continue;

				if ( ( typeof ( vars [ t ] ) == 'string' ) || ( typeof ( vars [ t ] ) == 'number' ) || ( typeof ( vars [ t ] ) == 'boolean' ) )
				{
					res += t + "=" + this._ajax_escape ( vars [ t ] ) + "&";
				} else {
					try 
					{
						res += t + "=" + this._ajax_escape ( vars [ t ].toJSONString() ) + "&";
					} catch ( e ) {
						res += t + "=" + this._ajax_escape ( vars [ t ] ) + "&";
					}
				}
			}

			res = res.substr ( 0, res.length - 1 ); //  + "&";
		}

		req = this._build_req_obj ();

		if ( ! req )
		{
			alert ( 'Your browser doesn\'t support AJAX request!' );
			return;
		}

		if ( ! err_cback )
		{
			if ( this.error_handler )
			{
				console.warn ( "AJAXManager.error_handler is DEPRECATED. Use cbacks [ 'req-error' ] instead." );
				err_cback = this.error_handler;
			} else {
				err_cback = this.cbacks [ 'req-error' ];
			}
		}

		var self = this;

		req.onreadystatechange = function () 
		{ 
			self._req_change ( req, callback, easy, err_cback ); 
		};

		var async = true;
		if ( sync ) async = false;

		// if (erroneously) the url starts with two "//", Firefox throws a security error
		url = url.replace ( /^\/\//g, "/" );

		// NEW: adding the action as a URI anchor
		// if ( vars && vars [ 'action' ] ) url += "#" + vars [ 'action' ];

		req.open ( "POST", url, async );
		req.setRequestHeader ( 'Content-Type', 'application/x-www-form-urlencoded' );
		req.setRequestHeader ( 'Cache-Control', 'no-cache' );
		req.send ( res );

		if ( easy )
		{
			if ( this.start_req_handler ) 
			{
				console.warn ( "AJAXManager.start_req_handler is DEPRECATED. Use cbacks [ 'req-start' ] instead." );
				this.start_req_handler ( req );
			}

			if ( this.cbacks [ 'req-start' ] ) this.cbacks [ 'req-start' ] ( req );
		}
	};
	// }}}
	// {{{ easy ( arr, cback )
/**
	.. method:: easy ( vars, callback )

		This is a simplified version of :meth:`~AJAXManager.request`.

		:param vars:	vars is a dictionary (key/value pairs) containing all the variable / params you want to send
				using the AJAX call.

		:param callback: since by default AJAX requests are asyncronous, you can set a ``callback`` to be called when
				 the request changes its status. The callback function must be defined as following:

					.. code-block:: javascript

						// This is an example of an ``easy`` request callback that will
						// be called only when the request succedes.
						function req_easy ( v )
						{
							// v is a object key/value pairs containing the response
							console.debug ( "V: %o", v );
						}

**/
	this.easy    = function ( arr, cback, err_cback ) { this.request ( null, arr, cback, true, false, err_cback ); };
	// }}}
	// {{{ _build_req_obj ()
	this._build_req_obj = function ()
	{
		var req;

		if ( ! req && typeof XMLHttpRequest != 'undefined' ) 
		{
			try {
				req = new XMLHttpRequest();
			} catch (e) {
				req = false;
			}
		}

		if ( ! req && window.createRequest ) 
		{
			try {
				req = window.createRequest();
			} catch (e) {
				req = false;
			}
		}

		if ( ! req ) 
		{
			try {
				req = new ActiveXObject ( "Microsoft.XMLHTTP" );
			} catch (e) {
				req = false;
			}
		}

		this._reqs.push ( req );

		return req;
	};
	// }}}
	// {{{ _req_change ( req, callback, easy, err_handler )
	this._req_change = function ( req, callback, easy, err_handler )
	{
		if ( ! easy ) 
		{
			if ( callback ) callback ( req );
		} else {
			var in_abort = this._in_abort;

			// Remove the req from the Request Pool
			if ( req.readyState == 4 ) 
			{
				if ( this.end_req_handler ) 
				{
					console.warn ( "AJAXManager.end_req_handler is DEPRECATED. Use cbacks [ 'req-end' ] instead." );
					this.end_req_handler ( req );
				}

				if ( this.cbacks [ 'req-end' ] ) this.cbacks [ 'req-end' ] ( req );

				this._remove_req ( req );

				if ( in_abort ) return;

				AJAXManager.handle_easy ( req.responseText, callback, err_handler );
			}
		}
	};
	// }}}

	this.jsonp = function ( url, args, cback, err_cback, jsonp_label )
	{
		var id = "jsonp_" + liwe.utils.unique_id ().replace ( /-/g, "_" );
		var  s = document.createElement('script');

		if ( ! args ) args = {};

		if ( ! jsonp_label ) jsonp_label = AJAXManager.JSONP_LABEL;

		args [ jsonp_label ] = id;
		// args [ 'JSONP' ] = id;
	
		args = this._prepare_vars ( args );

		window [ id ] = function ( response ) 
		{
			document.body.removeChild ( s );
			
			AJAXManager.handle_easy ( response, cback );
		};

		s.onerror = function ( e ) 
		{ 
			document.body.removeChild ( s );
			if ( err_cback ) 
				err_cback ( e, args );
			else
				console.error ( "JSONP REQUEST ERROR: %o - args: %o", e, args );
		};


		s.src = url + "?" + args;

		document.body.appendChild ( s );
	};

	this.error_handler = null;		// DEPRECATED
	this.start_req_handler = null;		// DEPRECATED
	this.end_req_handler = null;		// DEPRECATED

	this._prepare_vars = function ( vars )
	{
		var t, res = '';

		if ( ! vars ) return '';

		vars [ '_ts' ] = new Date ().getTime ();

		for ( t in vars ) 
		{
			if ( typeof ( vars [ t ] ) == 'undefined' ) continue;
			if ( typeof ( vars [ t ] ) == 'function' ) continue;
			if ( ( typeof ( vars [ t ] ) == 'object' ) && ( vars [ t ] == null ) ) continue;

			/*
				This is a hack for IE, since it considers functions as objects (!!!)
			*/
			if ( typeof ( vars [ t ] ) == 'object' )
			{
				s = vars [ t ].toString ();
				if ( s.match ( /^function/ ) ) continue;
			}

			if ( vars [ t ] == '__arr' ) continue;

			if ( ( typeof ( vars [ t ] ) == 'string' ) || ( typeof ( vars [ t ] ) == 'number' ) )
			{
				res += t + "=" + this._ajax_escape ( vars [ t ] ) + "&";
			} else {
				try 
				{
					res += t + "=" + this._ajax_escape ( vars [ t ].toJSONString() ) + "&";
				} catch ( e ) {
					res += t + "=" + this._ajax_escape ( vars [ t ] ) + "&";
				}
			}
		}

		res = res.substr ( 0, res.length - 1 ); //  + "&";

		return res;
	};

	// {{{ _remove_req ( req )
	this._remove_req = function ( req )
	{
		var t, l = this._reqs.length;

		if ( this._in_list ) 
		{
			var obj = this;

			setTimeout ( function () { obj._remove_req ( req ); }, 100 );
			return;
		}

		this._in_list = 1;

		for ( t = 0; t < l; t ++ )
		{
			if ( this._reqs [ t ] == req ) break;
		}
		if ( t < l )
			this._reqs.splice ( t, 1 );

		this._in_list = 0;
	};
	// }}}
	// {{{ abort ( cback )
/**
	.. method:: abort ( [ cback ] )

		Use this method to abort the currently running AJAX request.

		.. warning::
			This method aborts **all** AJAX requests currently running.

		:param cback: 	if you want to be notified when the request aborts, pass a valid function callback:
				The callback function must be defined as following:

					.. code-block:: javascript

						function abort_cback ()
						{
							// your code here
						}

**/
	this.abort = function ( cback )
	{
		var t, l = this._reqs.length;

		if ( this._in_list ) 
		{
			var obj = this;

			//console.debug ( "In list: " + this._in_list );
			setTimeout ( function () { obj.abort (); }, 100 );
			return;
		}

		this._in_list = 2;
		this._in_abort = true;

		for ( t = 0; t < l; t ++ )
		{
			try
			{
				this._reqs [ t ].abort ();
			} catch ( e ) {
			}
		}

		this._in_abort = false;
		this._in_list = 0;

		if ( cback ) cback ();
	};
	// }}}
	
	this._ajax_escape = function ( s )
	{
		if ( this.cbacks [ 'serialize' ] )
			s = this.cbacks [ 'serialize' ] ( s );

		//s = escape ( s );
		//s = s.replace ( /\+/g, "%2B" );

		return encodeURIComponent ( s );
	};
};

AJAXManager.handle_easy = function ( responseText, callback, err_handler )
{
	var _ajax_response = {}, func;
	if ( typeof ( responseText ) == 'string' )
	{
		var resp_txt = responseText.replace ( /^\s+/, "" );

		if ( resp_txt.substr ( 0, "var ajax".length ) == 'var ajax' )
		{
			try {
				eval ( resp_txt );
				_ajax_response = ajax_response;
			} catch ( e ) {
				if ( err_handler ) err_handler ( responseText );
				console.error ( "ERROR IN EVAL: %s - (except: %o)", responseText, e );
				return;
			}
		} else {
			if ( err_handler ) err_handler ( responseText );
			if ( responseText.length > 0 ) console.error ( "Request ERROR: " + responseText );
			//FIXME: setta errore interno ( pig ) 
			_ajax_response [ 'err_code' ] = '500';
			_ajax_response [ 'err_descr' ] = 'Internal server error';
		}
	} else {
		_ajax_response = responseText;
	}

	var err_code = _ajax_response [ 'err_code' ];

	if ( typeof ( err_code ) != 'undefined' && err_code != 0 )
	{
		if ( ! err_handler )
		{
			if ( _ajax_response [ 'err_descr' ] )
			{
				console.error ( _ajax_response [ 'err_descr' ] );
				alert ( _ajax_response [ 'err_descr' ] );
			} else {
				console.error ( "Generic Request error. Error code: " + _ajax_response [ 'err_code' ] );
			}
		} else {
			err_handler ( responseText, _ajax_response [ "err_descr" ], _ajax_response [ "err_code" ], _ajax_response );
		}

		return;
	}

	// patch for liwe.types
	if ( liwe.types ) liwe.types.to_dict ( _ajax_response, 2 );

	if ( callback && typeof ( callback ) == 'function' ) callback ( _ajax_response );

};

AJAXManager.JSONP_LABEL = 'JSONP';

liwe.AJAX = new AJAXManager ();

liwe.AJAX._multi = {};
liwe.AJAX._multi_data = {};

liwe.AJAX.add = function ( name, action, dict, cback )
{
	var multi;
	var data;

	if ( ! name   ) name = "AJAX";
	if ( ! action ) action = null;


	multi = this._multi [ name ];
	if ( ! multi ) 
	{
		multi = [];
		data  = {};
	} else {
		data = this._multi_data [ name ];
	}

	if ( data [ 'running' ] ) 
	{
		console.error ( "Requests are already running for: %s", name );
		return;
	}

	multi.push ( [ action, dict, cback ] );
	this._multi [ name ] = multi;
	this._multi_data [ name ] = data;

	console.debug ( "Multi: %s - Len: %d", name, multi.length );
};

liwe.AJAX.start = function ( name, cback )
{
	var multi, data, t, l, req, func;

	multi = this._multi [ name ];
	if ( ! multi ) 
	{
		console.error ( "There is no multi group called: %s", name );
		return;
	}

	data = this._multi_data [ name ];
	data [ 'running' ] = true;
	data [ 'len' ] = multi.length;
	data [ 'count' ] = 0;

	l = multi.length;
	var i;
	for ( t = 0; t < l; t ++ )
	{
		req = multi [ t ];

		liwe.AJAX._send ( name, req, cback );
	}
};

liwe.AJAX._send = function ( name, req, cback ) 
{
	this.request ( req [ 0 ], req [ 1 ], function ( v ) { liwe.AJAX._req_cback ( name, v, req, cback ); }, true );
};

liwe.AJAX._req_cback = function ( name, v, req, cback )
{
	var multi = liwe.AJAX._multi [ name ];
	var data = liwe.AJAX._multi_data [ name ];

	if ( req [ 2 ] ) req [ 2 ] ( v );
	

	data [ 'count' ] += 1;

	if ( data [ 'count' ] == data [ 'len' ] ) 
	{
		if ( cback ) cback ();
		liwe.AJAX._multi [ name ] = null;
		liwe.AJAX._multi_data [ name ] = null;
	}
};
